/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * The MySQL Connector/C++ is licensed under the terms of the GPLv2
 * <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
 * MySQL Connectors. There are special exceptions to the terms and
 * conditions of the GPLv2 as it is applied to this software, see the
 * FLOSS License Exception
 * <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

#ifndef MYSQLX_RESULT_H
#define MYSQLX_RESULT_H

/**
  @file
  Classes used to access query and command execution results.
*/


#include "common.h"
#include "document.h"
#include "row.h"
#include "collations.h"
#include "detail/result.h"

#include <memory>


namespace cdk {

  class Reply;

}  // cdk


namespace mysqlx {

using std::ostream;

class Session;
class Schema;
class Collection;
class Result;
class Row;
class RowResult;
class SqlResult;
class DbDoc;
class DocResult;

template <class Res, class Op> class Executable;


namespace internal {

  struct Session_detail;
  class Result_base;

} // internal


namespace internal {

  /*
    Base for result classes.
  */

  DLL_WARNINGS_PUSH

  class PUBLIC_API Result_base
    : public virtual Result_detail
  {

  DLL_WARNINGS_PUSH

  public:

    Result_base(Result_base &&other)
    {
      try {
        init(std::move(other));
      }
      CATCH_AND_WRAP
    }

    virtual ~Result_base() {}

    /// Get the number of warnings stored in the result.

    unsigned getWarningCount() const
    {
      try {
        return get_warning_count();
      }
      CATCH_AND_WRAP
    }

    /// Get a list of warnings stored in the result.

    WarningList getWarnings()
    {
      try {
        return get_warnings();
      }
      CATCH_AND_WRAP
    }

    /// Get the warning at the given, 0-based position.

    Warning getWarning(unsigned pos)
    {
      try {
        return get_warning(pos);
      }
      CATCH_AND_WRAP
    }

    // TODO: expose this in the API?
    //using WarningsIterator = Result_detail::iterator;

  protected:

    Result_base()
    {}

    void init(Result_base &&other)
    {
      Result_detail::init(std::move(other));
    }

  private:

    INTERNAL Result_base(Session *sess, cdk::Reply *r)
      : Result_detail(sess, r)
    {}

    INTERNAL Result_base(
      Session *sess, cdk::Reply *r,
      const std::vector<GUID> &guids
    )
      : Result_detail(sess, r, guids)
    {}

  public:

    ///@cond IGNORED

    friend mysqlx::internal::Session_detail;
    friend mysqlx::Result;
    friend mysqlx::RowResult;
    friend mysqlx::SqlResult;
    friend mysqlx::DocResult;

    struct INTERNAL Access;
    friend Access;

    ///@endcond
  };

}  // internal namespace


/**
  Represents a result of an operation that does not return data.

  A generic result which can be returned by operations which only
  modify data.

  A `Result` instance can store the result of executing an operation:

  ~~~~~~
  Result res = operation.execute();
  ~~~~~~

  Storing another result in a `Result` instance overwrites
  the previous result.

  @ingroup devapi_res
*/

class PUBLIC_API Result : public internal::Result_base
{
  using DocIdList = internal::List_init<GUID>;

public:

  Result() = default;

  Result(Result &&other)
  {
    init(std::move(other));
  }

  Result& operator=(Result &&other)
  {
    init(std::move(other));
    return *this;
  }

  /**
    Get the count of affected items from manipulation statements.
  */

  uint64_t getAffectedItemsCount() const;

  /**
    Get the auto-increment value if one was generated by a table insert
    statement.
  */

  uint64_t getAutoIncrementValue() const;

  /**
    Return an identifier of a single document added to a collection.
  */

  const GUID& getDocumentId() const;

  /**
    Return a list of identifiers of multiple documents added to a collection.
  */

  DocIdList getDocumentIds() const;

private:

  Result(Result_base &&other)
  {
    init(std::move(other));
  }

  void init(Result_base &&other)
  {
    Result_base::init(std::move(other));
  }

  template <class Res, class Op>
  friend class Executable;
};



// Row based results
// -----------------


// RowResult column meta-data
// --------------------------

/**
  List of types for `Type` enumeration. For each type TTT in this list
  there is the corresponding enumeration value `Type::TTT`. For example,
  constant `Type::INT` represents the "INT" type.

  @note The class name declared for each type is ignored for now
  -- it is meant for future extensions.

  @ingroup devapi_res
*/

#define TYPE_LIST(X) \
  X(BIT,        BlobType)     \
  X(TINYINT,    IntegerType)  \
  X(SMALLINT,   IntegerType)  \
  X(MEDIUMINT,  IntegerType)  \
  X(INT,        IntegerType)  \
  X(BIGINT,     IntegerType)  \
  X(FLOAT,      NumericType)  \
  X(DECIMAL,    NumericType)  \
  X(DOUBLE,     NumericType)  \
  X(JSON,       Type)         \
  X(STRING,     StringType)   \
  X(BYTES,      BlobType)     \
  X(TIME,       Type)         \
  X(DATE,       Type)         \
  X(DATETIME,   Type)         \
  X(TIMESTAMP,  Type)         \
  X(SET,        StringType)   \
  X(ENUM,       StringType)   \
  X(GEOMETRY,   Type)         \

#undef TYPE_ENUM
#define TYPE_ENUM(T,X) T,


/**
  Types that can be reported in result meta-data.

  @ingroup devapi_res
*/

enum class Type : unsigned short
{
  TYPE_LIST(TYPE_ENUM)
};


#define TYPE_NAME(T,X) case Type::T: return #T;

/**
  Return name of a given type.

  @ingroup devapi_res
*/

inline
const char* typeName(Type t)
{
  switch (t)
  {
    TYPE_LIST(TYPE_NAME)
  default:
    THROW("Unknown type");
  }
}

inline
std::ostream& operator<<(std::ostream &out, Type t)
{
  return out << typeName(t);
}


/**
  Provides meta-data for a single result column.

  @ingroup devapi_res
*/

class PUBLIC_API Column
  : public internal::Printable
  , internal::Column_detail
{
public:

  Column(Column_detail &&other)
    : Column_detail(std::move(other))
  {}

  string getSchemaName()  const;  ///< TODO
  string getTableName()   const;  ///< TODO
  string getTableLabel()  const;  ///< TODO
  string getColumnName()  const;  ///< TODO
  string getColumnLabel() const;  ///< TODO

  Type getType()   const;  ///< TODO

  /**
    Get column length

    @return The maximum length of data in the column in bytes, as reported
    by the server.

    @note Because the column length is returned as byte length, it could be
    confusing with the multi-byte character sets. For instance, with UTF8MB4
    the length of VARCHAR(100) column is reported as 400 because each character
    is 4 bytes long.
  */

  unsigned long getLength() const;
  unsigned short getFractionalDigits() const;  ///< TODO
  bool isNumberSigned() const;  ///< TODO

  CharacterSet getCharacterSet() const;  ///< TODO

  /// TODO
  std::string getCharacterSetName() const
  {
    return characterSetName(getCharacterSet());
  }

  const CollationInfo& getCollation() const;  ///< TODO

  /// TODO
  std::string getCollationName() const
  {
    return getCollation().getName();
  }

  /// TODO
  bool isPadded() const;

private:

  virtual void print(std::ostream&) const;

public:

  friend RowResult;
  struct INTERNAL Access;
  friend Access;
};


/**
  %Result of an operation that returns rows.

  A `RowResult` object gives sequential access to the rows contained in
  the result. It is possible to get the rows one-by-one, or fetch and store
  all of them at once. One can iterate over the rows using range loop:
  `for (Row r : result) ...`.

  @ingroup devapi_res
*/

class PUBLIC_API RowResult
    : public internal::Result_base
    , internal::Row_result_detail
{
  using RowList = internal::List_initializer<RowResult>;

public:

  RowResult()
  {}

  RowResult(RowResult &&other)
  {
    init(std::move(other));
  }

  virtual ~RowResult() {}

  RowResult& operator=(RowResult &&other)
  {
    init(std::move(other));
    return *this;
  }

  /// Return the number of fields in each row.

  col_count_t getColumnCount() const;

  /// Return `Column` object describing the given column of the result.

  Column getColumn(col_count_t pos) const;

  /**
    Return meta-data for all result columns.

    The returned data can be stored in any container which holds `Column`
    objects, such as `std::vector<Column>`.
  */

  Columns getColumns() const
  {
    try {
      return Columns(*this);
    }
    CATCH_AND_WRAP
  }

  /**
    Return the current row and move to the next one in the sequence.

    If there are no more rows in this result, returns a null `Row` instance.
  */

  Row fetchOne()
  {
    return get_row();
  }

  using iterator = Row_result_detail::iterator;

  /**
    Return all remaining rows

    %Result of this method can be stored in a container such as
    `std::list<Row>`. Rows that have already been fetched using `fetchOne()` are
    not included in the result of `fetchAll()`.
   */

  RowList fetchAll()
  {
    return internal::List_initializer<RowResult>(*this);
  }

  /**
    Returns the number of rows contained in the result.

    The method counts only the rows that were not yet fetched and are still
    available in the result.
  */

  uint64_t count();

  /*
   Iterate over rows (range-for support).

   Rows that have been fetched using iterator are not available when
   calling fetchOne() or fetchAll()
  */

  using Row_result_detail::begin;
  using Row_result_detail::end;

private:

  RowResult(Result_base &&other)
  {
    init(std::move(other));
  }


  void init(Result_base &&other)
  {
    Result_base::init(std::move(other));
    // note: no additional initialization of Row_result_detail
  }

public:

  template <class Res, class Op>
  friend class Executable;
  friend SqlResult;
  friend DocResult;
};


/**
  %Result of an SQL query or command.

  In general, an SQL query or command can return multiple results (for example,
  a call to a stored procedure). Additionally, each or only some of these
  results can contain row data. A `SqlResult` object gives a sequential access
  to all results of a multi-result. Method `nextResult()` moves to the next
  result in the sequence, if present. Methods of `RowResult` are used to access
  row data of the current result (if it contains data).

  @ingroup devapi_res
*/

class PUBLIC_API SqlResult : public RowResult
{
public:

  SqlResult(SqlResult &&other)
  {
    init(std::move(other));
  }

  SqlResult& operator=(SqlResult &&other)
  {
    init(std::move(other));
    return *this;
  }


  /**
    Tell if the current result contains row data.

    If this is the case, rows can be accessed using `RowResult` interface.
    Otherwise calling `RowResult` methods throws an error.
  */

  bool hasData() const;


  /**
    Move to the next result, if there is one.

    Returns true if the next result is available, false if there are no more
    results in the reply. Calling `nextResult()` discards the current result.
    If it has any rows that has not yet been fetched, these rows are also
    discarded.
  */

  bool nextResult();

  /**
    Get the count of affected items from data manipulation statements.
  */

  uint64_t getAffectedRowsCount();

  /**
    Get the auto-increment value if one was generated by a table insert
    statement.
  */

  uint64_t getAutoIncrementValue();

private:

  SqlResult(Result_base &&other)
  {
    init(std::move(other));
  }

  void init(Result_base &&other)
  {
    RowResult::init(std::move(other));
  }

  void init(SqlResult &&other)
  {
    RowResult::init(std::move(other));
  }

  template <class Res, class Op>
  friend class Executable;
};


// Document based results
// ----------------------


/**
  %Result of an operation that returns documents.

  A `DocResult` object gives sequential access to the documents contained in
  the result. It is possible to get the documents one-by-one, or fetch and store
  all of them at once. One can iterate over the documents using range loop:
  `for (DbDoc d : result) ...`.

  @ingroup devapi_res
*/

class PUBLIC_API DocResult
  : public internal::Result_base
  , internal::Doc_result_detail
{
  using DocList = internal::List_initializer<DocResult>;

public:

  DocResult()
  {}

  DocResult(DocResult &&other)
  {
    init(std::move(other));
  }

  void operator=(DocResult &&other)
  {
    try {
    init(std::move(other));
    }
    CATCH_AND_WRAP
  }

  /**
    Return the current document and move to the next one in the sequence.

    If there are no more documents in this result, returns a null document.
  */

  DbDoc fetchOne()
  {
    return get_doc();
  }

  /**
    Return all remaining documents.

    %Result of this method can be stored in a container such as
    `std::list<DbDoc>`. Documents that have already been fetched using
    `fetchOne()` are not included in the result of `fetchAll()`.
   */

  DocList fetchAll()
  {
    return internal::List_initializer<DocResult>(*this);
  }

  /**
    Returns the number of documents contained in the result.

    The method counts only the documents that were not yet fetched and are still
    available in the result.
  */

  uint64_t count()
  {
    return Doc_result_detail::count();
  }

  /*
   Iterate over documents (range-for support).

   Documents that have been fetched using iterator are not available when
   calling fetchOne() or fetchAll()
  */

  using iterator = Doc_result_detail::iterator;

  using Doc_result_detail::begin;
  using Doc_result_detail::end;

private:

  DocResult(Result_base &&other)
  {
    init(std::move(other));
  }

  void init(Result_base &&other)
  {
    Result_base::init(std::move(other));
  }

  friend DbDoc;
  template <class Res,class Op>
  friend class Executable;
};


}  // mysqlx

#endif
